﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace server_logic_simulator
{
    public class Character
    {
        //-----------------------------------------------------------------
        // Persistent attributes
        //-----------------------------------------------------------------

        // Character info
        public int mID;
        public string mName;
        public int mExp;
        public int mLevel;
        public Career mCareer;

        // First grade attributes point allocation
        public int mMightAlloc;
        public int mAgilityAlloc;
        public int mIntelligenceAlloc;
        public int mStaminaAlloc;
        public int mToughnessAlloc;

        // Equipments
        public Equipment mWeapon;
        public Equipment mArmor0;
        public Equipment mArmor1;
        public Equipment mArmor2;
        public Equipment mArmor3;
        public Equipment mArmor4;

        public enum SkillSelectMode { RandomMode = 0, GearMode = 1, PriorityMode = 2};
        // Skill select mode
        public SkillSelectMode mSkillSelectMode;

        // Learned skills
        public List<Skill> mLearnedSkills;

        // Equiped skills
        public List<Skill> mEquipedSkills;
        public Skill mBasicAttackSkill;
        
        //-----------------------------------------------------------------
        // Runtime variable attribute 
        //-----------------------------------------------------------------
        public int mMight;
        public int mAgility;
        public int mIntelligence;
        public int mStamina;
        public int mToughness;

        public int mAttack;
        public int mDefence;
        public int mMagicAttack;
        public int mMagicDefence;

        public int mMaxHP;
        public int mHP;

        public int mDodgeRate;
        public int mHitRate;

        public int mCriticalRate;
        public int mCriticalRatio;
        public int mResilience;

        public int mCounterAttackRate;
        public int mCounterAttackRatio;
        public int mSneakAttack;

        public int mBlockRate;
        public int mBlockRatio;
        public int mBrokenShield;

        // Upper/Lower limit of weapon damage
        public int mWeaponDamageUL;
        public int mWeaponDamageLL;
        public int mWeaponMagicDamageUL;
        public int mWeaponMagicDamageLL;

        private class CooldownSkill
        {
            public Skill mSkill;
            public int mCooldownRemain;
        }
        public ArrayList mSkillReadyList, mSkillNoReadyList;



        private void CalculateFirstGradeAttributes()
        {
            // First grade attribute: Total = basic + growth * level / 100 + character alloc + equipment add
            mMight = mCareer.mBasicMight + mCareer.mMightGrowth * mLevel / 100 + mMightAlloc +
                                mWeapon.mMightAdd + mArmor0.mMightAdd + mArmor1.mMightAdd + 
                                mArmor2.mMightAdd + mArmor3.mMightAdd + mArmor4.mMightAdd;

            mAgility = mCareer.mBasicAgility + mCareer.mAgilityGrowth * mLevel / 100 + mAgilityAlloc +
                                mWeapon.mAgilityAdd + mArmor0.mAgilityAdd + mArmor1.mAgilityAdd +
                                mArmor2.mAgilityAdd + mArmor3.mAgilityAdd + mArmor4.mAgilityAdd;

            mIntelligence = mCareer.mBasicIntelligence + mCareer.mIntelligenceGrowth * mLevel + mIntelligenceAlloc +
                                mWeapon.mIntelligenceAdd + mArmor0.mIntelligenceAdd + mArmor1.mIntelligenceAdd +
                                mArmor2.mIntelligenceAdd + mArmor3.mIntelligenceAdd + mArmor4.mIntelligenceAdd;

            mStamina = mCareer.mBasicStamina + mCareer.mStaminaGrowth * mLevel + mStaminaAlloc +
                                mWeapon.mStaminaAdd + mArmor0.mStaminaAdd + mArmor1.mStaminaAdd + 
                                mArmor2.mStaminaAdd + mArmor3.mStaminaAdd + mArmor4.mStaminaAdd;

            mToughness = mCareer.mBasicToughness + mCareer.mToughnessGrowth * mLevel + mToughnessAlloc +
                                mWeapon.mToughnessAdd + mArmor0.mToughnessAdd + mArmor1.mToughnessAdd + 
                                mArmor2.mToughnessAdd + mArmor3.mToughnessAdd + mArmor4.mToughnessAdd;        
        }

        private void CalculateSecondGradeAttributes()
        {
            // Second grade attribute: Total = basic + growth * level / 100 + equipment add
            mAttack = mCareer.mBasicAttack + mCareer.mAttackGrowth * mLevel / 100 +
                        mWeapon.mAttackAdd + mArmor0.mAttackAdd + mArmor1.mAttackAdd + 
                        mArmor2.mAttackAdd + mArmor3.mAttackAdd + mArmor4.mAttackAdd;

            mDefence = mCareer.mBasicDefence + mCareer.mDefenceGrowth * mLevel / 100 +
                        mWeapon.mDefenceAdd + mArmor0.mDefenceAdd + mArmor1.mDefenceAdd + 
                        mArmor2.mDefenceAdd + mArmor3.mDefenceAdd + mArmor4.mDefenceAdd;

            mMagicAttack = mCareer.mBasicMagicAttack + mCareer.mMagicAttackGrowth * mLevel / 100 +
                        mWeapon.mMagicAttackAdd + mArmor0.mMagicAttackAdd + mArmor1.mMagicAttackAdd + 
                        mArmor2.mMagicAttackAdd + mArmor3.mMagicAttackAdd + mArmor4.mMagicAttackAdd;

            mMagicDefence = mCareer.mBasicMagicDefence + mCareer.mMagicDefenceGrowth * mLevel / 100 +
                        mWeapon.mMagicDefenceAdd + mArmor0.mMagicDefenceAdd + mArmor1.mMagicDefenceAdd + 
                        mArmor2.mMagicDefenceAdd + mArmor3.mMagicDefenceAdd + mArmor4.mMagicDefenceAdd;   
 
            //MaxHP should plus stamina x 10
            mMaxHP = mCareer.mBasicMaxHP + mCareer.mMaxHPGrowth * mLevel / 100 +
                        mWeapon.mMaxHPAdd + mArmor0.mMaxHPAdd + mArmor1.mMaxHPAdd + 
                        mArmor2.mMaxHPAdd + mArmor3.mMaxHPAdd + mArmor4.mMaxHPAdd +
                        mStamina * 10;  //mStamina should be calculate first
        }

        private void CalculateAdditionalAttributes()
        {
            // Additional attribute: Total = basic + equipment add
            mDodgeRate = mCareer.mBasicDodgeRate +
                        mWeapon.mDodgeRateAdd + mArmor0.mDodgeRateAdd + mArmor1.mDodgeRateAdd + 
                        mArmor2.mDodgeRateAdd + mArmor3.mDodgeRateAdd + mArmor4.mDodgeRateAdd;      
            mHitRate = mCareer.mBasicHitRate +
                        mWeapon.mHitRateAdd + mArmor0.mHitRateAdd + mArmor1.mHitRateAdd + 
                        mArmor2.mHitRateAdd + mArmor3.mHitRateAdd + mArmor4.mHitRateAdd; 
                   
            mCriticalRate = mCareer.mBasicCriticalRate +
                        mWeapon.mCriticalRateAdd + mArmor0.mCriticalRateAdd + mArmor1.mCriticalRateAdd + 
                        mArmor2.mCriticalRateAdd + mArmor3.mCriticalRateAdd + mArmor4.mCriticalRateAdd;             
            mCriticalRatio = mCareer.mBasicCriticalRatio +
                        mWeapon.mCriticalRatioAdd + mArmor0.mCriticalRatioAdd + mArmor1.mCriticalRatioAdd + 
                        mArmor2.mCriticalRatioAdd + mArmor3.mCriticalRatioAdd + mArmor4.mCriticalRatioAdd;             
            mResilience = mCareer.mBasicResilience +
                        mWeapon.mResilienceAdd + mArmor0.mResilienceAdd + mArmor1.mResilienceAdd + 
                        mArmor2.mResilienceAdd + mArmor3.mResilienceAdd + mArmor4.mResilienceAdd; 

                   
            mCounterAttackRate = mCareer.mBasicCounterAttackRate +
                        mWeapon.mCounterAttackRateAdd + mArmor0.mCounterAttackRateAdd + mArmor1.mCounterAttackRateAdd + 
                        mArmor2.mCounterAttackRateAdd + mArmor3.mCounterAttackRateAdd + mArmor4.mCounterAttackRateAdd; 
            mCounterAttackRatio = mCareer.mBasicCounterAttackRatio +
                        mWeapon.mCounterAttackRatioAdd + mArmor0.mCounterAttackRatioAdd + mArmor1.mCounterAttackRatioAdd + 
                        mArmor2.mCounterAttackRatioAdd + mArmor3.mCounterAttackRatioAdd + mArmor4.mCounterAttackRatioAdd;         
            mSneakAttack = mCareer.mBasicSneakAttack +
                        mWeapon.mSneakAttackAdd + mArmor0.mSneakAttackAdd + mArmor1.mSneakAttackAdd + 
                        mArmor2.mSneakAttackAdd + mArmor3.mSneakAttackAdd + mArmor4.mSneakAttackAdd; 

                   
            mBlockRate = mCareer.mBasicBlockRate +
                        mWeapon.mBlockRateAdd + mArmor0.mBlockRateAdd + mArmor1.mBlockRateAdd + 
                        mArmor2.mBlockRateAdd + mArmor3.mBlockRateAdd + mArmor4.mBlockRateAdd;             
            mBlockRatio = mCareer.mBasicBlockRatio +
                        mWeapon.mBlockRatioAdd + mArmor0.mBlockRatioAdd + mArmor1.mBlockRatioAdd + 
                        mArmor2.mBlockRatioAdd + mArmor3.mBlockRatioAdd + mArmor4.mBlockRatioAdd; 
            mBrokenShield = mCareer.mBasicBrokenShield +
                        mWeapon.mBrokenShieldAdd + mArmor0.mBrokenShieldAdd + mArmor1.mBrokenShieldAdd + 
                        mArmor2.mBrokenShieldAdd + mArmor3.mBrokenShieldAdd + mArmor4.mBrokenShieldAdd; 

        }

        private void SetWeaponDamage()
        {
            mWeaponDamageUL = mWeapon.mWeaponDamageUL;
            mWeaponDamageLL = mWeapon.mWeaponDamageLL;
            mWeaponMagicDamageUL = mWeapon.mWeaponMagicDamageUL;
            mWeaponMagicDamageLL = mWeapon.mWeaponMagicDamageLL;         
        }

		// Init ready and noready skill list, fill ready list with equiped skills
        private void InitCooldownSkillList()
        {
            mSkillReadyList = new ArrayList();
            mSkillNoReadyList = new ArrayList();
            foreach(Skill skill in mEquipedSkills)
            {
                CooldownSkill cooldown_skill = new CooldownSkill();
                cooldown_skill.mSkill = skill;
                cooldown_skill.mCooldownRemain = 0;

                mSkillReadyList.Add(cooldown_skill);
            }
        }

        // If persistent attribute change, this function should be called.
        public void InitRuntimeAttributes()
        {
            CalculateFirstGradeAttributes();
            CalculateSecondGradeAttributes();
            CalculateAdditionalAttributes();
            SetWeaponDamage();
            InitCooldownSkillList();
        }

        // Set all equiped skills usable immediately
        public void ResetSkillCooldown()
        {
            foreach(CooldownSkill cooldown_skill in mSkillNoReadyList)
                Move2ReadyList(cooldown_skill);
        }


        // randomly select skill
        private CooldownSkill GetRandomModeSkill()
        {
            int idx;
            CooldownSkill cooldown_skill;

            idx = Utils.Random(0, mSkillReadyList.Count);
            cooldown_skill = mSkillReadyList[idx] as CooldownSkill;            

            return cooldown_skill;
        }

        // select skill depend on characher's gear mode
        private CooldownSkill GetGearModeSkill()
        {
            int idx;
            CooldownSkill cooldown_skill;

            // TODO: replace with gear mode logic
            idx = Utils.Random(0, mSkillReadyList.Count);
            cooldown_skill = mSkillReadyList[idx] as CooldownSkill;

            return cooldown_skill;
        }

        // select priority skill
        private CooldownSkill GetPriorityModeSkill()
        {
            int idx;
            CooldownSkill cooldown_skill;

            // TODO: replace with priority mode logic
            idx = Utils.Random(0, mSkillReadyList.Count);
            cooldown_skill = mSkillReadyList[idx] as CooldownSkill;

            return cooldown_skill;
        }

        private void Move2NoReadyList(CooldownSkill cooldown_skill)
        {
            mSkillReadyList.Remove(cooldown_skill);
            cooldown_skill.mCooldownRemain = cooldown_skill.mSkill.mCooldownRound;  //reset cooldown
            mSkillNoReadyList.Add(cooldown_skill);
        }

        private void Move2ReadyList(CooldownSkill cooldown_skill)
        {
            cooldown_skill.mCooldownRemain = 0;     //set cooldown to 0
            mSkillNoReadyList.Remove(cooldown_skill);
            mSkillReadyList.Add(cooldown_skill);
        }

        // select skill and set cooldown(if need)
        private Skill SelectSkill()
        {
            CooldownSkill cooldown_skill = null;

            // all skill cooldown, use Normal Attack
            if (mSkillReadyList.Count == 0)
                return mBasicAttackSkill;

            // select skill
            if (mSkillSelectMode == SkillSelectMode.RandomMode)
                cooldown_skill = GetRandomModeSkill();
            else if (mSkillSelectMode == SkillSelectMode.GearMode)
                cooldown_skill = GetGearModeSkill();
            else if (mSkillSelectMode == SkillSelectMode.PriorityMode)
                cooldown_skill = GetPriorityModeSkill();
            else
                Debug.Fail("Skill Select Mode " + mSkillSelectMode + "is not a correct value!");
            // set skill in cooldown
            if(cooldown_skill != null)
                Move2NoReadyList(cooldown_skill);

            return cooldown_skill.mSkill;
        }

        // reduce cooldown of no ready skills, and move skill to the ready list if cooldown end
        private void ReduceSkillCooldown()
        {
            int cooldown_remain;
            foreach(CooldownSkill cooldown_skill in mSkillNoReadyList)
            {
                cooldown_remain = cooldown_skill.mCooldownRemain - 1;
                if(cooldown_remain > 0)
                    cooldown_skill.mCooldownRemain = cooldown_remain;
                else
                    Move2ReadyList(cooldown_skill);
            }
        }

        // Turn to action
        public void TakeAction(Battle battle)
        {
            Skill casting_skill;
            List<Character> target_list;

            // reduce cooldown of no ready skills, and move skill to the ready list if cooldown end
            ReduceSkillCooldown();

            // select skill and set cooldown(if need)
            casting_skill = SelectSkill();
			
            if (casting_skill.mTargetSelectMode == Skill.TargetSelectMode.AllianceMode)
            {
                // select targets
                target_list = casting_skill.SelectTargets(casting_skill, battle.mAllianceTeam);
                // buffer or cure alliance
                foreach (Character target in target_list)
                    target.GetCure(this, casting_skill);
            }
            else
            { 
                // select targets
                target_list = casting_skill.SelectTargets(casting_skill, battle.mEnemyTeam);
                // hurt enemy
                foreach (Character target in target_list)
                    target.GetHurt(this, casting_skill);
            }
        }

	    private int ApplyBlockEffect(int damage, Character attacker)
	    {
		    int result;
            int ratio = Utils.Clamp(this.mBlockRatio - attacker.mBrokenShield, 0, 100);
		    result = damage - damage * ratio / 100;
    		
		    return result;		
	    }

	    private int ApplyCriticalEffect(int damage, Character attacker)
	    {
		    int result;
            int ratio = Utils.Clamp((attacker.mCriticalRatio - this.mResilience), 0, 100);
		    result = damage + damage * ratio / 100;
    			
		    return result;
	    }

        private bool IsDodge(Character attacker)
        {
            int dodgeRate = Utils.Clamp(mDodgeRate - attacker.mHitRate, 0, 100);
            if (Utils.Random(0, 100) < dodgeRate)
                return true;
            else
                return false;
        }

        // judge attack effect type: 0-normal, 1-block, 2-critical, 3-dodge
        private int DecideAttackEffectType(Character attacker)
        {
            int dodgeRate, blockRate, criticalRate;
            int ret_attack_effect = 0;  

            // Calculate whether attack be dodged
            dodgeRate = Utils.Clamp(mDodgeRate - attacker.mHitRate, 0, 100);
            if (Utils.Random(0, 100) < dodgeRate)
                ret_attack_effect = 3;
            else
            {
                // calculate whether attack be blocked, or be a critical strike
                blockRate = Utils.Clamp(mBlockRate - attacker.mBrokenShield, 0, 100);
                criticalRate = Utils.Clamp(attacker.mCriticalRate - mResilience, 0, 100);
                // calculate damage result
                int temp;
		        if(blockRate + criticalRate > 100)
		        {
                    temp = Utils.Random(0, blockRate + criticalRate);
			        if(temp < blockRate)
                        ret_attack_effect = 1;
			        else
                        ret_attack_effect = 2;
		        }
		        else
		        {
                    temp = Utils.Random(0, 100);
			        if(temp < blockRate)
                        ret_attack_effect = 1;
			        else if(temp < blockRate + criticalRate)			   		   
                        ret_attack_effect = 2;
		        }
            }
            return ret_attack_effect;
        }

        // reduce HP and check whether dead
        public bool GetHurt(Character attacker, Skill damage_skill)
        {
            int damage, attack_effect;
            bool ret_isDead = false;

			//  dodge ? block ? critical attack ?
            attack_effect = DecideAttackEffectType(attacker);
			
		    if(attack_effect < 3)  // no dodge
            {
                // calculate skill damage
                damage = damage_skill.CalculateSkillDamage(attacker, this);   
                
                if(attack_effect == 1)   // block attack
                    damage = ApplyBlockEffect(damage, attacker);
                else if(attack_effect == 2) // critical attack
                    damage = ApplyCriticalEffect(damage, attacker);

                // reduce HP
		        mHP = damage > mHP ? 0 : (mHP - damage);
                if (mHP == 0)
                    ret_isDead = true;
            }

            return ret_isDead;
        }

        public void GetCure(Character healer, Skill cure_skill)
        {
            // calculate Healing value 

            // increase HP

            // print result
        }
    }
}
